當我們在使用手機時經常會收到來自於不同應用程式的各種通知。包括由遠程的服務發送的通知(如:Line 訊息、Facebook 好友邀請通知等),以及來自於本機的通知(如:系統使用空間不足、系統更新完成等)。
這兩者在本質上有著很大的區別:
我們應用程式目前有使用到 firebase 的authentication 服務,其實 firebase 也有提供推送遠程通知的功能 Firebase Cloud Messaging
。要使其在iOS 與 Android 裝置上實現是相當容易的,Firebase 官方也有提供完整的文件教各位如何實現。但難就難在若要於 iOS 裝置上接收遠程通知,同樣的得先加入 Apple Developer Program,花一波錢成為開發者,才能繼續將設置完成... 所以我們目前也同樣先繞過這個方案,先實作來自於本機的通知訊息吧!
首先請先下載以下套件
flutter pub add flutter_local_notifications
該套件是一個可以輕易實現本機通知的套件,針對跨平台的支援也相當不錯。因此我們將會以此作為我們通知的媒介。
不過想要在 Android 及 iOS 平台上使用通知服務,還需要分別對兩者進行一些設定。
請開啟 android/app/src/main/AndroidManifest.xml
檔案,我們需要在 <activity>
標籤中加入以下設定:
<activity android.name=".MainActivity" ....>
<!-- 會有 meta-data 及其他 intent-filter,就直接加在後方 -->
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
請開啟 ios/Runner/AppDelegate.swift
// 首先上方先 import 所需函式
import flutter_local_notifications
//
{
// 加入這行
FlutterLocalNotificationsPlugin.setPluginRegistrantCallback {
registry in GeneratedPluginRegistrant.register(with: registry)
}
GeneratedPluginRegistrant.register(with: self)
// 也加入這行
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
}
請在 lib
底下建立一個 plugins
資料夾,於其中建立一個檔案 notification.dart
,並請參考下方程式碼:
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationPlugin {
final FlutterLocalNotificationsPlugin np = FlutterLocalNotificationsPlugin();
init() async {
// 在 Android 平台上的設置,並使用 flutter logo 作為通知顯示的圖標
var android = const AndroidInitializationSettings('flutter_logo');
// 在 iOS 平台上的設置,並設置了 iOS 通知的權限
var ios = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
onDidReceiveLocalNotification: (id, title, body, payload) async {},
);
// 註冊不同平台初始化設置的 InitializationSetting 對象
var initSettings = InitializationSettings(android: android, iOS: ios);
// 使用 np 實例的 initialize 方法初始化本地通知插件
await np.initialize(
initSettings,
onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) async {}
);
}
// 觸發通知時,透過呼叫此函式來顯示通知
Future showNotification(
{int id = 0, String? title, String? body, String? payload}) async {
return np.show(id, title, body, await notificatinoDetails());
}
notificatinoDetails() async {
return const NotificationDetails(
android: AndroidNotificationDetails('channelId', 'channelName',
importance: Importance.max),
iOS: DarwinNotificationDetails(),
);
}
}
接下來請開啟 main.dart
的主函式:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// 加入這行,使得 NotificationPlugin 呼叫 init 將本地通知註冊於應用程式中
await NotificationPlugin().init();
runApp(ChangeNotifierProvider(
create: (context) => ThemeModel(), child: const MyApp()));
}
接下來讓我們發出第一則通知吧。我們希望當開啟「每日一報」功能時,跳出一個通知表示該功能已開啟。請開啟 profile_screen.dart
// 請移至每日一報的 CupertinoSwitch
CupertinoSwitch(
value: _isDailyReport,
onChanged: (bool? value) {
if (value == true) {
NotificationPlugin().showNotification(
title: '每日一報',
body: '您訂閱的新聞已更新',
);
}
setState(() {
_isDailyReport = value!;
});
})),
當 switch 狀態開啟時,則跳出通知。結果如下圖:
當你第一次觸發 iOS 平台的通知時,系統會詢問是否允許通知,請點選允許。Android 平台我自己在測試時有發現不一定會跳出詢問通知的對話框,因預設皆為不允許所以可能都不會跳出通知。請至手機的通知設定頁面將該應用的通知功能開啟。
假設我今天在提醒事項的應用程式中設定 20 分鐘後提醒我要記得發一篇鐵人賽文章,那麼在設定的當下就是將這個提醒的通知進行排程,並計算 20 分鐘後觸發。
flutter_local_notification
同樣也有此支援,讓我們來看看要怎麼做吧。
請打開 main.dart
,並添加以下程式碼:
// 引入 timezone 相關的套件
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
void main() async {
tz.initializeTimeZones(); // 初始化時區資料庫
tz.setLocalLocation(tz.getLocation('Asia/Taipei')); // 將時區設定為台北標準時間
// 以下皆維持原狀故省略
}
請開啟 notification.dart
檔案,並於 class 中加入以下程式碼:
Future showScheduledNotification(
{int id = 0,
String? title,
String? body,
String? payload,
required DateTime scheduledDate}) async {
return np.zonedSchedule(
id,
title,
body,
tz.TZDateTime.from(scheduledDate, tz.local),
await notificatinoDetails(),
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime);
}
與原先的 showNotification
大致無異。唯一有差別的為新增傳入一參數,用於表示要觸發該通知的時間戳記。
如此便可以使用該函式進行排程的通知,如下:
showScheduledNotification(
title: '提醒我記得發鐵人賽文章',
body: '第 23 天了,快要結束了~~',
// 將排程時間設為現在之後的 20 分鐘
scheduledDate: DateTime.now().add(const Duration(minutes: 20)),
);
這裡讓我們補充一下 np.zonedSchedule
的 androidScheduleMode
與 uiLocalNotificationDateInterpretation
吧!
此一參數用於確定發送通知的精確程度。為什麼需要提供此一參數呢?發通知不是很簡單的事情嗎?其實不然,從 Android 6.0 版本之後提供了兩種省電模式,稱作 Doze
與 App Standby
。
Doze
按字面照翻就是小歇一下,也就是當手機未處於充電狀態、閒置一段時間且螢幕關閉時,為了延續電池壽命,系統會限制應用程式取用網路資源或是過度消耗 CPU 運算的工作。當螢幕再次點亮或是系統自動同步的維護時間週期一到時,才會再次的進行同步處理,並允許應用程式取用網路。
當維護完成後,系統會再次歇息,並暫停網路、同步處理等等的工作。系統會隨著時間逐漸拉長維護工作的週期。
App Standby
則是系統判斷將未使用的應用程式切換的待命模式。
該參數提供以下幾種屬性可以進行設定
屬性 | 通知時間 | 低功率環境下是否執行 | 是否需特殊權限 |
---|---|---|---|
alarmClock | 精確時間 | 是 | 需 SCHEDULE_EXAVT_ALARM 權限 |
exact | 精確時間 | 否 | 否 |
exactAllowWhileIdle | 精確時間 | 是 | 否 |
inexact | 大致精確時間 | 否 | 否 |
inexactAllowWhileIdle | 大致精確時間 | 是 | 否 |
用於支援 iOS 版本低於 10 的設定,提供兩種屬性 absoluteTime
表示標準時間;wallClockTime
表示當前裝置時間。
了解完後,讓我們來測試一下吧!請開啟 profile_screen.dart
並移至每日一報觸發通知的函式,修改成以下程式碼:
// 請移至每日一報的 CupertinoSwitch
CupertinoSwitch(
value: _isDailyReport,
onChanged: (bool? value) {
if (value == true) {
NotificationPlugin().showScheduledNotification(
title: '每日一報',
body: '您可以試試把應用程式從後台移除,你應該還是會接收到通知',
);
}
setState(() {
_isDailyReport = value!;
});
})),
不過目前的通知都是只有單次性的,一但觸發完畢就結束了。所以想要達成「每日一報」,我們需要使用到的是週期性通知。
方法為 periodicallyShow()
,需要額外傳入一參數 RepeatInterval
,其屬性有:
請參考下列程式碼:
Future showPeriodicNotification(
{int id = 0,
String? title,
String? body,
String? payload,
required RepeatInterval interval}) async {
return np.periodicallyShow(
id,
title,
body,
interval,
await notificatinoDetails(),
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
);
因此我們就可以串接此函式來達成每日提醒拉~
// 請移至每日一報的 CupertinoSwitch
CupertinoSwitch(
value: _isDailyReport,
onChanged: (bool? value) {
if (value == true) {
NotificationPlugin().showPeriodicNotification(
title: '每日一報',
body: '我們為您準備好了專屬於您的新聞,歡迎您來看看!',
interval: RepeatInterval.daily
);
}
setState(() {
_isDailyReport = value!;
});
})),
今天我們算是完整的認識並使用了 flutter_local_notifications
的功能,可以方便我們用於實現本機的通知。
不過有些人可能會覺得尚有些單調,由於其只能顯示 title, body 等純文字內容。這裡也給各位一個替代的套件,名叫 awesome_notifications,就可以做出圖文並茂的通知拉!就交給有興趣的讀者自行研究。
今天的參考程式碼:https://github.com/ChungHanLin/micro_news_tutorial/tree/day23/micro_news_app